<!DOCTYPE html>
<html lang="en">
  
  <head>
    <meta charset="UTF-8">
    <title>Dagre Layout</title>
  </head>
  <style>
    .g6-tooltip {
      border: 1px solid #e2e2e2;
      border-radius: 4px;
      font-size: 12px;
      color: #545454;
      background-color: rgba(255, 255, 255, 0.9);
      padding: 10px 8px;
      box-shadow: rgb(174, 174, 174) 0px 0px 10px;
    }
  </style>
  <body>
    <div id="mountNode"></div>
    <script src="../build/dagre.js"></script>
    <script src="../build/g6.js"></script>
    <script>
      const data = {
        nodes: [{
          id: '1',
          label: '1'
        },
        {
          id: '2',
          label: '2'
        },
        {
          id: '3',
          label: '3'
        },
        {
          id: '4',
          label: '4'
        },
        {
          id: '5',
          label: '5'
        },
        {
          id: '6',
          label: '6'
        },
        {
          id: '7',
          label: '7'
        },
        {
          id: '8',
          label: '8',
          isLeaf: true
        },
        {
          id: '9',
          label: '9',
          isLeaf: true
        },
        {
          id: '10',
          label: '10',
          isLeaf: true
        },
        {
          id: '11',
          label: '11',
          isLeaf: true
        },
        {
          id: '12',
          label: '12',
          isLeaf: true
        },
        {
          id: '13',
          label: '13',
          isLeaf: true
        },
        {
          id: '14',
          label: '14',
          isLeaf: true
        },
        {
          id: '15',
          label: '15',
          isLeaf: true
        },
        {
          id: '16',
          label: '16',
          isLeaf: true
        },
        {
          id: '17',
          label: '17',
          isLeaf: true
        },
        {
          id: '18',
          label: '18',
          isLeaf: true
        },
        {
          id: '19',
          label: '19',
          isLeaf: true
        }],
        edges: [{
          source: '1',
          target: '2'
        },
        {
          source: '1',
          target: '3'
        },
        {
          source: '3',
          target: '4'
        },
        {
          source: '2',
          target: '5'
        },
        {
          source: '5',
          target: '6'
        },
        {
          source: '5',
          target: '7'
        },
        {
          source: '7',
          target: '8'
        },
        {
          source: '6',
          target: '9'
        },
        {
          source: '6',
          target: '10'
        },
        {
          source: '6',
          target: '11'
        },
        {
          source: '6',
          target: '12'
        },
        {
          source: '6',
          target: '13'
        },
        {
          source: '6',
          target: '14'
        },
        {
          source: '4',
          target: '15'
        },
        {
          source: '4',
          target: '16'
        },
        {
          source: '4',
          target: '17'
        },
        {
          source: '4',
          target: '18'
        },
        {
          source: '4',
          target: '19'
        }]
      };

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 1000,
        height: 700,
        modes: {
          default: ['drag-node', 'drag-canvas']
        },
        defaultNode: {
          shape: 'rect',// hexagon
          color: 'steelblue'
        },
        defaultEdge: {
          style: {
            stroke: '#aaa'
          }
        }
      });
      const nodes = data.nodes;
      const edges = data.edges;
      const nodeMap = new Map();
      const branchNodes = [];
      const branchEdges = [];
      const leafGroups = {};
      const branchNodesMap = new Map();
      const leafGroupOffset = 100;
      // separate the leaves and branch nodes
      nodes.forEach((node, i) => {
        nodeMap.set(node.id, node);
        if (node.isLeaf) {
          node.size = 30;
          node.shape = 'circle';
          edges.forEach(edge => {
            if (edge.source === node.id) {
              node.parent = edge.target;
            } else if (edge.target === node.id) {
              node.parent = edge.source;
            }
          });
          if (!leafGroups[node.parent]) leafGroups[node.parent] = [];
          leafGroups[node.parent].push(node);
        } else {
          branchNodes.push(node);
          branchNodesMap.set(node.id, node);
        }
      });
      edges.forEach(edge => {
        if (branchNodesMap.get(edge.source) !== undefined
          && branchNodesMap.get(edge.target) !== undefined) {
            branchEdges.push(edge);
        }
      });
      // layout the nodes with isLeaf === false by dagre
      const dagreLayout = new G6.Layout['dagre']({
        nodesep: 50,
        ranksep: 10
      });
      dagreLayout.init({ nodes: branchNodes, edges: branchEdges });
      dagreLayout.execute();

      //layout the leaves with isLeaf === true by force
      const forceLayoutsMap = new Map();
      Object.keys(leafGroups).forEach(function(parentId){
        const lg = leafGroups[parentId];
        const parent = branchNodesMap.get(parentId);
        const center = [ parent.x, parent.y + leafGroupOffset ];
        const forceLayout = new G6.Layout['force']({
          center,
          linkDistance: 0,
          nodeStrength: 30,
          collideStrength: 0.7,
          alphaDecay: 0.01,
          preventOverlap: true,
          tick: () => {
            graph.refreshPositions();
          }
        });
        forceLayout.init({ nodes: lg, edges: [] });
        forceLayout.execute();
        forceLayoutsMap.set(parentId, forceLayout);
      });

      graph.positionsAnimate();

      graph.data(data);
      graph.render();

      function refreshDragedNodePosition(e) {
        const model = e.item.get('model');
        model.fx = e.x;
        model.fy = e.y;
      }
      graph.on('node:dragstart', e => {
        const model = e.item.get('model');
        if (model.isLeaf) {
          const layoutMethod = forceLayoutsMap.get(model.parent);
          layoutMethod.ticking = false;
          layoutMethod.forceSimulation.stop();
          layoutMethod.execute();
          refreshDragedNodePosition(e);
        }
      });
      graph.on('node:drag', e => {
        const model = e.item.get('model');
        if (model.isLeaf) {
          refreshDragedNodePosition(e);
        }
      });
      graph.on('node:dragend', e => {
        const model = e.item.get('model');
        if (model.isLeaf) {
          e.item.get('model').fx = null;
          e.item.get('model').fy = null;
        }
      });
    </script>
  </body>
</html>